/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "xpm_integrity_test.h"

#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>

#include "file_ex.h"
#include "xpm_common.h"

using namespace std;
using namespace std::chrono;
using namespace testing;
using namespace testing::ext;

namespace OHOS {
namespace Security {
namespace XpmUnitTest {
static int g_fd;
static struct XpmRegionArea g_area;
static unsigned long g_dataOffset, g_dataSize;
static unsigned long g_codeOffset, g_codeSize;

void XpmIntegrityTest::SetUpTestCase()
{
    SaveStringToFile(SELINUX_MODE_PATH, PERMISSIVE_MODE);
    g_fd = open(XPM_LIB_PATH.c_str(), O_RDWR);
    ASSERT_NE(g_fd, -1) << "open xpm lib failed: " << strerror(errno);

    int ret = GetDataSegment(XPM_LIB_PATH, g_dataOffset, g_dataSize);
    ASSERT_EQ(0, ret) << "GetDataSegment failed: " << ret;

    ret = GetCodeSegment(XPM_LIB_PATH, g_codeOffset, g_codeSize);
    ASSERT_EQ(0, ret) << "GetCodeSegment failed: " << ret;

    SaveStringToFile(XPM_DEBUG_FS_MODE_PATH, ENFORCE_MODE);
}

void XpmIntegrityTest::TearDownTestCase()
{
    close(g_fd);
    SaveStringToFile(XPM_DEBUG_FS_MODE_PATH, PERMISSIVE_MODE);
    SaveStringToFile(SELINUX_MODE_PATH, ENFORCE_MODE);
}

void XpmIntegrityTest::SetUp()
{
}

void XpmIntegrityTest::TearDown()
{
}

/**
 * @tc.number 38.ReadBeforeWrite
 * @tc.name: FileRemapRoToRwTest
 * @tc.desc: ABC代码同时映射到xpm region外，并设置写权限
 * @tc.result: ABC代码映射成功，但在尝试写的时候（先读后写），缺页中断处理产生XPM完整性保护异常
 */
HWTEST_F(XpmIntegrityTest, FileRemapRoToRBWTest, TestSize.Level0)
{
    // 1. 映射为r--
    void *addr1 = mmap(NULL, g_dataOffset, PROT_READ, MAP_SHARED | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr1);

    // 2. 可以读操作
    int ret = AccessData(addr1, OP_READ);
    EXPECT_EQ(0, ret);

    // 3. remap到外部r-w区域
    void *addr2 = mmap(NULL, g_dataOffset, PROT_READ | PROT_WRITE, MAP_SHARED, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr2);

    // 4. 写操作异常
    EXPECT_EXIT(AccessData(addr2, OP_WRITE), KilledBySignal(SIGSEGV), "");

    munmap(addr1, g_dataOffset);
    munmap(addr2, g_dataOffset);
}


/**
 * @tc.number 38.OnlyWrite
 * @tc.name: FileRemapRoToRwTest
 * @tc.desc: ABC代码同时映射到xpm region外，并设置写权限
 * @tc.result: ABC代码映射成功，但在尝试写的时候（只写），缺页中断处理产生XPM完整性保护异常
 */
HWTEST_F(XpmIntegrityTest, FileRemapRoToRWOTest, TestSize.Level0)
{
    // 1. 映射为r--
    void *addr1 = mmap(NULL, g_dataOffset, PROT_READ, MAP_SHARED | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr1);

    // 2. 可以读操作
    int ret = AccessData(addr1, OP_READ);
    EXPECT_EQ(0, ret);

    // 3. remap到外部r-w区域
    void *addr2 = mmap(NULL, g_dataOffset, PROT_READ | PROT_WRITE, MAP_SHARED, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr2);

    // 4. 写操作异常
    EXPECT_EXIT(AccessData(addr2, OP_WRITE_ONLY), KilledBySignal(SIGSEGV), "");

    munmap(addr1, g_dataOffset);
    munmap(addr2, g_dataOffset);
}

/**
 * @tc.number 39
 * @tc.name: FileRemapOutRwToInRoTest
 * @tc.desc: ABC代码先映射到xpm region外，设置写权限，更新后，再映射到xpm region内，设置为只读
 * @tc.result: ABC代码映射到xpm region外成功，并可以成功更新，但在xpm_region内尝试读的时候，缺页中断处理产生XPM完整性保护异常
 */
HWTEST_F(XpmIntegrityTest, FileRemapOutRwToInRoTest, TestSize.Level0)
{
    // 1. 映射到外部r-w区域
    void *addr1 = mmap(NULL, g_dataOffset, PROT_READ | PROT_WRITE, MAP_SHARED, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr1);

    // 2. 写操作正常
    int ret = AccessData(addr1, OP_WRITE);
    EXPECT_EQ(ret, 0);

    // 3. 映射到region 内设置为r--
    void *addr2 = mmap(NULL, g_dataOffset, PROT_READ, MAP_SHARED | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr2);

    // 4. 读操作触发完整性保护，执行异常
    EXPECT_EXIT(AccessData(addr2, OP_READ), KilledBySignal(SIGSEGV), "");

    // release resource
    munmap(addr1, g_dataOffset);
    munmap(addr2, g_dataOffset);
}

// 11.解析文件得到代码段信息.share
// 应用为映射代码段内容（代码）的可写区域（rw-）
// 先删除（mprotect）可写权权限（r--）
// 然后增加（mprotect）可执行权限（r-x）
// 映射操作会被放行，在运行时，页完整性阻止执行
HWTEST_F(XpmIntegrityTest, SharedFileMprotectRWToROToRXTest, TestSize.Level0)
{
  int ret;

  //1. 映射代码为rw-
  void *addr = mmap(NULL, g_codeSize, PROT_READ | PROT_WRITE, MAP_SHARED, g_fd, g_codeOffset & PAGE_MASK);
  EXPECT_NE(MAP_FAILED, addr);

  //1.1 修改代码内容, 触发页污点
  ret = AccessData(addr, OP_WRITE);
  EXPECT_EQ(0, ret); //通过

  //2. 删除代码写权限 --> r--
  ret = mprotect(addr, g_codeOffset & PAGE_MASK, PROT_READ);
  EXPECT_EQ(0, ret);

  //3. 增加代码执行权限 --> r-x
  ret = mprotect(addr, g_codeOffset & PAGE_MASK, PROT_READ | PROT_EXEC);
  EXPECT_EQ(0, ret);

//   //4. 执行对应代码, 失败
  EXPECT_EXIT(ExecSumFun(addr), KilledBySignal(SIGSEGV), "");

  munmap(addr, g_codeSize);
}

// 11.解析文件得到代码段信息.private
// 应用为映射代码段内容（代码）的可写区域（rw-）
// 先删除（mprotect）可写权权限（r--）
// 然后增加（mprotect）可执行权限（r-x）
// 映射操作会被放行，在运行时，页完整性阻止执行
HWTEST_F(XpmIntegrityTest, PrivateFileMprotectRWToROToRXTest, TestSize.Level0)
{
  int ret;

  //1. 映射代码为rw-
  void *addr = mmap(NULL, g_codeSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, g_fd, g_codeOffset & PAGE_MASK);
  EXPECT_NE(MAP_FAILED, addr);

  //1.1 修改代码内容, 触发页污点
  ret = AccessData(addr, OP_WRITE);
  EXPECT_EQ(0, ret); //通过

  //2. 删除代码写权限 --> r--
  ret = mprotect(addr, g_codeOffset & PAGE_MASK, PROT_READ);
  EXPECT_EQ(0, ret);

  //3. 增加代码执行权限 --> r-x
  ret = mprotect(addr, g_codeOffset & PAGE_MASK, PROT_READ | PROT_EXEC);
  EXPECT_EQ(0, ret);

//   //4. 执行对应代码, 失败
  EXPECT_EXIT(ExecSumFun(addr), KilledBySignal(SIGSEGV), "");

  munmap(addr, g_codeSize);
}


/**
 * @tc.number 17.shared
 * @tc.name: BIN_NormalFileMprotectCodeRwToRwxTest
 * @tc.desc: 二进制普通文件代码段文件映射的rw-权限内存通过mremap修改为r-x权限
 * @tc.result: 修改操作成功，执行代码段时失败，代码段不允许先设置rw权限，后设置rx权限
 */
HWTEST_F(XpmIntegrityTest, SharedFileRemapCodeRwToRxTest, TestSize.Level0)
{
    int ret;
    unsigned long offset, size;

    void *addr1 = mmap(NULL, g_codeSize, PROT_READ | PROT_WRITE, MAP_SHARED, g_fd, g_codeOffset & PAGE_MASK);
    ASSERT_NE(MAP_FAILED, addr1);

    //1.1 修改代码内容, 触发页污点
    ret = AccessData(addr1, OP_WRITE);
    EXPECT_EQ(0, ret); //通过

    void *addr2 = mmap(NULL, g_codeSize, PROT_READ | PROT_EXEC, MAP_SHARED, g_fd, g_codeOffset & PAGE_MASK);
    ASSERT_NE(MAP_FAILED, addr2);

    //4. 执行对应代码, 失败
    EXPECT_EXIT(ExecSumFun(addr2), KilledBySignal(SIGSEGV), "");

    // release resource
    munmap(addr1, g_codeSize);
    munmap(addr2, g_codeSize);
}


/**
 * @tc.number 17.private
 * @tc.name: BIN_NormalFileMprotectCodeRwToRwxTest
 * @tc.desc: 二进制普通文件代码段文件映射的rw-权限内存通过mremap修改为r-x权限
 * @tc.result: 修改操作成功，执行代码段时失败，代码段不允许先设置rw权限，后设置rx权限
 */
HWTEST_F(XpmIntegrityTest, PrivateFileRemapCodeRwToRxTest, TestSize.Level0)
{
    void *addr1 = mmap(NULL, g_codeSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, g_fd, g_codeOffset & PAGE_MASK);
    ASSERT_NE(MAP_FAILED, addr1);

    //1.1 修改代码内容, 触发页拷贝，对应页被标记为WT
    int ret = AccessData(addr1, OP_WRITE);
    EXPECT_EQ(0, ret); //通过

    void *addr2 = mmap(NULL, g_codeSize, PROT_READ | PROT_EXEC, MAP_SHARED, g_fd, g_codeOffset & PAGE_MASK);
    ASSERT_NE(MAP_FAILED, addr2);


    //4. 执行对应代码, 对应页被标记为RO，可以被执行
    ret = ExecSumFun(addr2);
    EXPECT_EQ(0, ret); //通过

    // release resource
    munmap(addr1, g_codeSize);
    munmap(addr2, g_codeSize);

    close(g_fd);
}

} // namespace XpmUnitTest
} // namespace Security
} // namespace OHOS